package cn.demoncat.util.kuaidi100;

import cn.demoncat.util.kuaidi100.config.Kuaidi100Properties;
import cn.demoncat.util.lang.constant.StatusConstant;
import cn.demoncat.util.lang.entity.ListStr;
import com.google.gson.Gson;
import com.kuaidi100.sdk.api.*;
import com.kuaidi100.sdk.contant.ApiInfoConstant;
import com.kuaidi100.sdk.pojo.HttpResult;
import com.kuaidi100.sdk.request.*;
import com.kuaidi100.sdk.response.*;
import com.kuaidi100.sdk.utils.SignUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * 快递100工具
 *
 * @author 延晓磊
 * @since 2021年04月06日
 */
@Component("kuaidi100Util")
public class Kuaidi100Util {

    @Resource
    private Kuaidi100Properties kuaidi100Properties;

    // 必传收件电话的快递公司：顺丰系
    private static final List<String> PHONE_REQUIRED_EXPRESS = ListStr.getInstance("shunfeng","fengwang","nsf","shunfengkuaiyun","shunfenglengyun");

    private static final Logger LOG = LoggerFactory.getLogger(Kuaidi100Util.class);

    /**
     * 是否必传收件人电话
     *
     * @param com  快递公司编码
     * @return 顺丰速运、顺丰快运、丰网速运返回True
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public static boolean isPhoneRequired(String com){
        return PHONE_REQUIRED_EXPRESS.contains(com);
    }

    /**
     * 查看启用状态
     *
     * @return
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public boolean getStatus(){
        return kuaidi100Properties.getStatus();
    }

    /**
     * 查询物流轨迹
     *
     * 快递状态：0在途，1揽收，2疑难，3签收，4退签，5派件，6退回，7转投，10待清关，11清关中，12已清关，13清关异常，14拒签
     *
     * @param com   快递公司编码
     * @param code  快递单号
     * @param phone 收件人的电话号码，顺丰速运、顺丰快运、丰网速运必填
     *
     * @return  异常：null
     *          错误：{result:false, returnCode:错误码, message:错误消息}
     *          正确：{result:true, state:快递状态, ischeck:是否签收, com:快递公司编码, nu:快递单号, data:[{context:明细, time:时间}]}
     *
     * @author 延晓磊
     * @since 2021年04月06日             
     */
    public QueryTrackResp queryTrack(String com, String code, String phone){
        if (!kuaidi100Properties.getStatus()) {
            // 测试环境
            return null;
        }
        LOG.info("请求快递100 - 查询物流轨迹：com = {}, code = {}", com, code);
        // 参数
        QueryTrackParam param = new QueryTrackParam();
        // - 快递公司编码
        param.setCom(com);
        // - 快递单号
        param.setNum(code);
        // - 收件人的电话号码，顺丰单号必填
        param.setPhone(phone);
        // 请求
        QueryTrackReq req = new QueryTrackReq();
        req.setParam( new Gson().toJson(param));
        req.setCustomer(kuaidi100Properties.getCustomer());
        req.setSign(SignUtils.querySign(req.getParam() ,kuaidi100Properties.getKey(), kuaidi100Properties.getCustomer()));
        // 执行
        HttpResult httpResult;
        try {
            // 请求成功，可能业务失败
            httpResult = new QueryTrack().execute(req);
        } catch (Exception e) {
            // 请求失败
            LOG.error("请求快递100失败 - 查询物流轨迹", e);
            return null;
        }
        // 解析
        if (httpResult.getStatus() == HttpStatus.SC_OK && StringUtils.isNotBlank(httpResult.getBody())){
            QueryTrackResp r = new Gson().fromJson(httpResult.getBody(), QueryTrackResp.class);
            if (StringUtils.isNotBlank(r.getState())) {
                // 正确：result = true, message = ok
                r.setResult(true);
            }
            return r;
        }
        LOG.warn("请求快递100失败 - 查询物流轨迹：result = " + httpResult);
        return null;
    }

    /**
     * 订阅物流轨迹
     *
     * @param id    数据ID，拼接在回调地址后
     * @param com   快递公司编码
     * @param code  快递单号
     * @param phone 收件人的电话号码，顺丰速运、顺丰快运、丰网速运必填
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public void subscribeTrack(String id, String com, String code, String phone){
        if (!kuaidi100Properties.getStatus()) {
            // 测试环境
            return;
        }
        LOG.info("请求快递100 - 订阅物流轨迹：com = {}, code = {}", com, code);
        // 附加参数
        SubscribeParameters subscribeParam = new SubscribeParameters();
        // - 回调地址
        subscribeParam.setCallbackurl(kuaidi100Properties.getTrackCallbackUrl() + id);
        // - 收件人的电话号码，顺丰单号必填
        subscribeParam.setPhone(phone);
        // - 签名参数
        subscribeParam.setSalt(kuaidi100Properties.getKey());
        // 参数
        SubscribeParam param = new SubscribeParam();
        param.setParameters(subscribeParam);
        param.setKey(kuaidi100Properties.getKey());
        // - 快递公司编码
        param.setCompany(com);
        // - 快递单号
        param.setNumber(code);
        // 请求
        SubscribeReq req = new SubscribeReq();
        req.setSchema(ApiInfoConstant.SUBSCRIBE_SCHEMA);
        req.setParam( new Gson().toJson(param));
        // 执行
        try {
            // 请求成功，可能业务失败
            new Subscribe().subscribe(req);
        } catch (Exception e) {
            // 请求失败
            LOG.error("请求快递100失败 - 订阅物流轨迹", e);
        }
    }

    /**
     * 订阅物流轨迹 - 通知处理（POST请求）
     *
     * @param request
     * @param consumer  通知数据处理：{status:是否结束，lastResult:最新结果}
     *
     * @return  通知返回结果
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public SubscribeResp subscribeTrackCallback(HttpServletRequest request, Consumer<SubscribePushParamResp> consumer){
        // 获取参数
        String param = request.getParameter("param");
        String sign = request.getParameter("sign");
        // 签名
        if (sign == null || SignUtils.sign(param + kuaidi100Properties.getKey()).equals(sign)){
            // 解析结果
            SubscribePushParamResp resp = new Gson().fromJson(param, SubscribePushParamResp.class);
            // 是否结束
            resp.setStatus(StatusConstant.Common.check("shutdown".equalsIgnoreCase(resp.getStatus())));
            // 业务处理
            consumer.accept(resp);
            // 通知成功
            SubscribeResp subscribeResp = new SubscribeResp();
            subscribeResp.setResult(true);
            subscribeResp.setReturnCode("200");
            subscribeResp.setMessage("成功");
            return subscribeResp;
        }
        LOG.warn("处理快递100通知失败 - 订阅物流轨迹：param = {}, sign = {}", param, sign);
        return null;
    }

    /**
     * 订阅物流轨迹 - 通知处理（POST请求）
     *
     * @param request
     * @param consumer  通知数据处理：{state,data}
     *
     * @return  通知返回结果
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public SubscribeResp subscribeTrackCallbacks(HttpServletRequest request, Consumer<QueryTrackResp> consumer){

        return subscribeTrackCallback(request, s -> {
            SubscribePushResult sr = s.getLastResult();
            // 转换为查询结果
            QueryTrackResp qr = new QueryTrackResp();
            qr.setState(sr.getState());
            qr.setIscheck(sr.getIscheck());
            qr.setNu(sr.getNu());
            qr.setData(sr.getData().stream().map(sd ->{
                QueryTrackData qd = new QueryTrackData();
                qd.setContext(sd.getContext());
                qd.setTime(sd.getTime());
                qd.setFtime(sd.getFtime());
                qd.setStatusCode(sd.getStatusCode());
                return qd;
            }).collect(Collectors.toList()));
            // 执行业务
            consumer.accept(qr);
        });
    }

    /**
     * 查询物流地图
     *
     * 快递状态：0在途，1揽收，2疑难，3签收，4退签，5派件，6退回，7转投，10待清关，11清关中，12已清关，13清关异常，14拒签
     *
     * @param com   快递公司编码
     * @param code  快递单号
     * @param phone 收件人的电话号码，顺丰速运、顺丰快运、丰网速运必填
     * @param from  出发地信息，如：北京朝阳区国际金融大厦
     * @param to    目的地信息，如：北京市市辖区朝阳区
     *
     * @return  异常：null
     *          错误：{result:false, returnCode:错误码, message:错误消息}
     *          正确：{result:true, trailUrl:地图链接, arrivalTime:预计到达时间, state:快递状态, ischeck:是否签收, com:快递公司编码, nu:快递单号, data:{context:明细, time:时间}}
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public QueryTrackMapResp queryMapTrack(String com, String code, String phone, String from, String to){
        if (!kuaidi100Properties.getStatus()) {
            // 测试环境
            return null;
        }
        LOG.info("请求快递100 - 查询物流地图：com = {}, code = {}", com, code);
        // 参数
        QueryTrackParam param = new QueryTrackParam();
        // - 快递公司编码
        param.setCom(com);
        // - 快递单号
        param.setNum(code);
        // - 收件人的电话号码，顺丰单号必填
        param.setPhone(phone);
        // - 起止地址，由快递100根据名称抽取
        param.setFrom(from);
        param.setTo(to);
        // 请求
        QueryTrackReq req = new QueryTrackReq();
        req.setParam( new Gson().toJson(param));
        req.setCustomer(kuaidi100Properties.getCustomer());
        req.setSign(SignUtils.querySign(req.getParam() ,kuaidi100Properties.getKey(), kuaidi100Properties.getCustomer()));
        // 执行
        HttpResult httpResult;
        try {
            // 请求成功，可能业务失败
            httpResult = new QueryTrackMap().execute(req);
        } catch (Exception e) {
            // 请求失败
            LOG.error("请求快递100失败 - 查询物流地图", e);
            return null;
        }
        // 解析
        if (httpResult.getStatus() == HttpStatus.SC_OK && StringUtils.isNotBlank(httpResult.getBody())){
            QueryTrackMapResp r = new Gson().fromJson(httpResult.getBody(), QueryTrackMapResp.class);
            if (StringUtils.isNotBlank(r.getState())) {
                // 正确：result = true, message = ok
                r.setResult(true);
            }
            return r;
        }
        LOG.warn("请求快递100失败 - 查询物流地图：result = " + httpResult);
        return null;
    }

    /**
     * 订阅物流地图
     *
     * @param id    数据ID，拼接在回调地址后
     * @param com   快递公司编码
     * @param code  快递单号
     * @param phone 收件人的电话号码，顺丰速运、顺丰快运、丰网速运必填
     * @param from  出发地信息，如：北京市朝阳区
     * @param to    目的地信息，如：北京市市辖区朝阳区
     *
     * @return  异常：null
     *          错误：{result:false, returnCode:错误码, message:错误消息}
     *          正确：{result:true}
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public SubscribeResp subscribeMapTrack(String id, String com, String code, String phone, String from, String to){
        if (!kuaidi100Properties.getStatus()) {
            // 测试环境
            return null;
        }
        LOG.info("请求快递100 - 订阅物流轨迹：com = {}, code = {}", com, code);
        // 附加参数
        SubscribeParameters subscribeParam = new SubscribeParameters();
        // - 回调地址
        subscribeParam.setCallbackurl(kuaidi100Properties.getMapTrackCallbackUrl() + id);
        // - 收件人的电话号码，顺丰单号必填
        subscribeParam.setPhone(phone);
        // - 签名参数
        subscribeParam.setSalt(kuaidi100Properties.getKey());
        // 参数
        SubscribeParam param = new SubscribeParam();
        param.setParameters(subscribeParam);
        param.setKey(kuaidi100Properties.getKey());
        // - 快递公司编码
        param.setCompany(com);
        // - 快递单号
        param.setNumber(code);
        // - 起止地址，由快递100根据名称抽取
        param.setFrom(from);
        param.setTo(to);
        // 请求
        SubscribeReq req = new SubscribeReq();
        req.setSchema(ApiInfoConstant.SUBSCRIBE_SCHEMA);
        req.setParam(new Gson().toJson(param));
        // 执行
        try {
            // 请求成功，可能业务失败
            return new SubscribeWithMap().subscribe(req);
        } catch (Exception e) {
            // 请求失败
            LOG.error("请求快递100失败 - 订阅物流地图", e);
            return null;
        }
    }

    /**
     * 订阅物流地图 - 通知处理（POST请求）
     *
     * @param request
     * @param consumer  通知数据处理：{status:是否结束，lastResult:最新结果}
     *
     * @return  通知返回结果
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public SubscribeResp subscribeMapTrackCallback(HttpServletRequest request, Consumer<SubscribeWithMapPushParamResp> consumer){
        // 获取参数
        String param = request.getParameter("param");
        String sign = request.getParameter("sign");
        // 签名
        if (sign == null || SignUtils.sign(param + kuaidi100Properties.getKey()).equals(sign)){
            // 解析结果
            SubscribeWithMapPushParamResp resp = new Gson().fromJson(param, SubscribeWithMapPushParamResp.class);
            // 是否结束
            resp.setStatus(StatusConstant.Common.check("shutdown".equalsIgnoreCase(resp.getStatus())));
            // 业务处理
            consumer.accept(resp);
            // 通知成功
            SubscribeResp subscribeResp = new SubscribeResp();
            subscribeResp.setResult(true);
            subscribeResp.setReturnCode("200");
            subscribeResp.setMessage("成功");
            return subscribeResp;
        }
        LOG.warn("处理快递100通知失败 - 订阅物流地图：param = {}, sign = {}", param, sign);
        return null;
    }

    /**
     * 查询快递公司编码
     *
     * 注：需要充值开通，否则返回601错误（KEY 已过期）
     *
     * @param code  快递单号
     * @return  异常：null。成功：[快递公司编码]
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public List<String> queryCom(String code){
        LOG.info("请求快递100 - 查询快递公司编码：code = {}", code);
        // 请求
        AutoNumReq req = new AutoNumReq();
        req.setKey(kuaidi100Properties.getKey());
        req.setNum(code);
        // 执行
        List<AutoNumResp> resp;
        try {
            // 请求成功，可能业务失败
            resp = new AutoNum().getComByNumList(req);
        } catch (Exception e) {
            // 请求失败
            LOG.error("请求快递100失败 - 查询快递公司编码", e);
            return null;
        }
        // 解析
        if (resp != null) {
            return resp.stream().map(AutoNumResp::getComCode).collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 查询快递公司编码
     *
     * @param com   编码
     * @param name  名称
     * @param code  快递号
     * @return  空表示不支持，非空表示智能匹配的编码
     *
     * @author 延晓磊
     * @since 2021年04月06日
     */
    public String queryCom(String com, String name, String code){
        if (StringUtils.isBlank(com)) {
            // 通过code匹配
            List<String> coms = queryCom(code);
            if (CollectionUtils.isNotEmpty(coms)) {
                if (StringUtils.isNotBlank(name)) {
                    for (String c : coms) {
                        if (c.equals(name)) {
                            // 匹配名称
                            return c;
                        }
                    }
                }
                // 匹配首选项
                return coms.get(0);
            }
        }
        return com;
    }
}
